package br.mia.unifor.onaga.cloud.execute;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Calendar;
import java.util.List;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.esotericsoftware.yamlbeans.YamlException;

import br.mia.unifor.onaga.cloud.CloudTestCase;
import br.mia.unifor.onaga.cloud.Test;
import br.mia.unifor.onaga.cloud.YamlLoader;
import br.mia.unifor.onaga.cloud.artifact.Appliance;
import br.mia.unifor.onaga.cloud.artifact.Instance;
import br.mia.unifor.onaga.cloud.artifact.MetricEval;
import br.mia.unifor.onaga.cloud.artifact.Scenario;
import br.mia.unifor.onaga.cloud.artifact.Workload;
import br.mia.unifor.onaga.cloud.client.Execution;
import br.mia.unifor.onaga.cloud.execute.factory.ComputeProviderFactory;
import br.mia.unifor.onaga.cloud.parser.OnagaParserYml;

public class Main implements Runnable {

	protected Logger logger = Logger.getLogger(getClass().getName());
	private final Test test;

	private Main(Test test) {
		this.test = test;
	}

	public static void execute(String testPath) throws YamlException,
			IOException {
		InputStream input = Main.class.getClass()
				.getResourceAsStream(testPath);
		if(input == null){			
			input = new FileInputStream(testPath);
		}
		
		OnagaParserYml parser = new OnagaParserYml(input);

		Test test = YamlLoader.loadTest(parser.processLineByLine());
		Main main = new Main(test);

		main.run();
	}

	private void stopInstances(List<Instance> instances) throws IOException {
		for (Instance instance : instances) {
			if (!"dummy".equals(instance.getType().getProvider())) {
				ComputeProvider provider = ComputeProviderFactory
						.getProvider(instance.getType().getProvider());

				provider.stopInstance(instance);

				logger.info(instance.getId() + " parada");
			}
		}
	}

	private void startInstances(Appliance appliance, List<Instance> instances)
			throws IOException {

		for (Instance instance : instances) {
			if (!"dummy".equals(instance.getType().getProvider().getName())) {
				ComputeProvider provider = ComputeProviderFactory
						.getProvider(instance.getType().getProvider());

				provider.startInstance(instance, appliance);

				logger.info(instance.getId() + " pronta");
			}
		}
	}

	private CloudTestCase getCloudTestCase(Scenario scenario) {
		CloudTestCase testCase = null;

		try {
			Class classe = this.getClass().forName(test.getClassName());

			return (CloudTestCase) classe.getConstructor(Scenario.class)
					.newInstance(scenario);

		} catch (ClassNotFoundException e) {
			logger.log(Level.SEVERE, "test class " + test.getClassName()
					+ " not found", e);
		} catch (IllegalArgumentException e) {
			logger.log(Level.SEVERE,
					"constructor of test class " + test.getClassName()
							+ " not found", e);
		} catch (SecurityException e) {
			logger.log(Level.SEVERE,
					"constructor of test class " + test.getClassName()
							+ " not found", e);
		} catch (InstantiationException e) {
			logger.log(Level.SEVERE,
					"constructor of test class " + test.getClassName()
							+ " not found", e);
		} catch (IllegalAccessException e) {
			logger.log(Level.SEVERE,
					"constructor of test class " + test.getClassName()
							+ " not found", e);
		} catch (InvocationTargetException e) {
			logger.log(Level.SEVERE,
					"constructor of test class " + test.getClassName()
							+ " not found", e);
		} catch (NoSuchMethodException e) {
			logger.log(Level.SEVERE,
					"constructor of test class " + test.getClassName()
							+ " not found", e);
		}

		return testCase;
	}

	public void run() {
		logger.info("inicio da execucao");

		logger.info("iniciando instancias do teste");

		try {

			startInstances(null, test.getInstances());

			for (Scenario scenario : test.getScenarios()) {
				try {

					startInstances(scenario.getAppliance(), scenario
							.getAppliance().getInstances());

					if (scenario.getAppliance().setUp()) {

					}

					execTests(scenario);

					scenario.getAppliance().teardown();

					List<Instance> instances = scenario.getAppliance()
							.getInstances();

					// removendo as instâncias globais
					for (int i = 0; i < instances.size(); ++i) {
						Instance instance = instances.get(i);
						if (test.getInstances().contains(instance)) {
							instances.remove(i);
						}
					}

					stopInstances(instances);
				} catch (Exception e) {
					logger.log(
							Level.SEVERE,
							"erro ao executar os testes no cenário "
									+ scenario.getId(), e);
				}
			}

			Thread.sleep(test.getInterval());

			stopInstances(test.getInstances());

		} catch (IOException e) {
			logger.log(Level.SEVERE, "erro ao carregar credenciais", e);
		} catch (InterruptedException e) {
			logger.log(Level.SEVERE,
					"problema para para a thread para a próxima rodada", e);
		}

		logger.info("fim da execucao");
	}

	private Boolean execTests(Scenario scenario) throws Exception,
			InterruptedException {
		Boolean retorno = Boolean.FALSE;
		
		CloudTestCase testCase = getCloudTestCase(scenario);

		scenario: for (Workload workload : scenario.getWorkloads()) {
			for (String workloadValue : workload.getValues()) {
				int round = 0;
				retorno = Boolean.FALSE;
				while (round < test.getRound()) {

					testCase.testRun(workloadValue);

					while (testCase.isRunning()) {
						Thread.sleep(10000);
					}

					List<MetricEval> results = testCase.getResult();
					Execution execution = new Execution();

					execution.setWhen(Calendar.getInstance());

					execution.setWorkload(workloadValue);

					retorno |= persistResults(execution, results);
					
					round++;
				}
				
				if(!retorno){
					logger.info("não houve nenhuma execução com sucesso na última carga");
					break scenario;
				}
			}
		}
		
		return retorno;

	}

	private Boolean persistResults(Execution execution, List<MetricEval> results) {
		Boolean passed = Boolean.TRUE;
		for (MetricEval metricEval : results) {
			execution.setInstanceType(metricEval.getDescription());
			execution.setName(metricEval.getId());
			passed &= metricEval.getPassed();
		}
		execution.setSuccess(passed);

		Execution.publishResult(execution);
		
		return passed;
	}

	public static void main(String[] args) {
		try {
			execute(args[0]);
		} catch (YamlException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
